﻿#region using

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;

#endregion

namespace CodeDom
{
  /// <summary>
  /// This code example creates a graph using a CodeCompileUnit and  
  /// generates source code for the graph using the CSharpCodeProvider.
  /// </summary>
  internal class TemplatesFileGenerator
  {
    private readonly IDictionary<string, CodeNamespace> _namespaces = new Dictionary<string, CodeNamespace>();
    private readonly string _rootFolder;
    private readonly string _rootNameSpace;
    private TemplateNameGenerator _nameGenerator;
    private CodeDomProvider _provider;
    private CodeCompileUnit _targetUnit;

    public TemplatesFileGenerator(string rootNameSpace, string rootFolder)
    {
      _rootNameSpace = rootNameSpace;
      _rootFolder = rootFolder;
      if (String.IsNullOrEmpty(rootNameSpace))
        throw new ArgumentNullException("rootNameSpace");

      InitializeProvider();
      InitializeNameGenerator();
      InitializeUnit();
    }

    public bool GenerateBaseTemplates { get; set; }
    protected bool GenerateInterfaces { get; set; }

    protected bool GenerateClasses { get; set; }

    private void InitializeNameGenerator()
    {
      _nameGenerator = new TemplateNameGenerator(_provider, _rootNameSpace, _rootFolder);
    }

    private void InitializeProvider()
    {
      _provider = CodeDomProvider.CreateProvider("CSharp");
    }

    private void InitializeUnit()
    {
      _targetUnit = new CodeCompileUnit();
    }

    private void GenerateCSharpCode(string fileName)
    {
      CodeGeneratorOptions options = new CodeGeneratorOptions
                                       {
                                         BracingStyle = "Block",
                                         IndentString = "  "
                                       };

      using (StreamWriter sourceWriter = new StreamWriter(fileName))
      {
        _provider.GenerateCodeFromCompileUnit(_targetUnit, sourceWriter, options);
      }
    }

    /// <summary>
    /// Create the CodeDOM graph and generate the code.
    /// </summary>
    public void Generate(IEnumerable<Template> templates, string fileName)
    {
      IEnumerable<Template> templatesToGenerate = templates;
      if (GenerateBaseTemplates)
        templatesToGenerate = templates.Union(templates.SelectMany(t => t.GetBaseTemplates(true))).Distinct(new TemplateEqualityComparer());

      GenerateTemplateCode(templatesToGenerate);
      GenerateCSharpCode(fileName);
    }

    private void GenerateTemplateCode(IEnumerable<Template> templates)
    {
      foreach (Template template in templates)
      {
        CodeNamespace targetNamespace = GetNamespaceForTemplate(template);
        if (GenerateInterfaces)
        { 
          TemplateInterfaceGenerator interfaceGenerator = new TemplateInterfaceGenerator(template,
                                                                                         new CodeTypeReference[] {},
                                                                                         _nameGenerator);
          CodeTypeDeclaration interfaceDeclaration = interfaceGenerator.Generate();
          targetNamespace.Types.Add(interfaceDeclaration);
          Console.WriteLine("Interface {0}.{1}", targetNamespace.Name, interfaceDeclaration.Name);
        }

        if (GenerateClasses)
        {
          TemplateClassGenerator classGenerator = new TemplateClassGenerator(template,
                                                                             new[]
                                                                               {
                                                                                 new CodeTypeReference(
                                                                                   _nameGenerator.GetInterfaceName(template))
                                                                               },
                                                                             _nameGenerator);
          CodeTypeDeclaration declaration = classGenerator.Generate();
          targetNamespace.Types.Add(declaration);
          Console.WriteLine("Class {0}.{1}", targetNamespace.Name, declaration.Name);
        }
      }
    }

    private CodeNamespace GetNamespaceForTemplate(Template template)
    {
      string namespaceName = _nameGenerator.GetNamespaceName(template);
      if (_namespaces.ContainsKey(namespaceName))
        return _namespaces[namespaceName];

      CodeNamespace targetNamespace = new CodeNamespace(namespaceName);
      AddNamespaceImports(targetNamespace);
      _targetUnit.Namespaces.Add(targetNamespace);
      _namespaces.Add(namespaceName, targetNamespace);

      targetNamespace.Comments.Add(
        new CodeCommentStatement(
          string.Format("<summary>\r\n Namespace for Sitecore folder {0}\r\n </summary>", template.Parent.Path), true));

      return targetNamespace;
    }

    private void AddNamespaceImports(CodeNamespace targetNamespace)
    {
      targetNamespace.Imports.Add(new CodeNamespaceImport("System"));
      targetNamespace.Imports.Add(new CodeNamespaceImport("System.Linq"));
      targetNamespace.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
      targetNamespace.Imports.Add(new CodeNamespaceImport("Sitecore"));
      targetNamespace.Imports.Add(new CodeNamespaceImport("Sitecore.Data"));
      targetNamespace.Imports.Add(new CodeNamespaceImport("Sitecore.Data.Items"));
      targetNamespace.Imports.Add(new CodeNamespaceImport("Sitecore.Data.Managers"));
      targetNamespace.Imports.Add(new CodeNamespaceImport("Sitecore.Data.Templates"));
      targetNamespace.Imports.Add(new CodeNamespaceImport("Sitecore.Diagnostics"));
      targetNamespace.Imports.Add(new CodeNamespaceImport("PT.Framework.ItemDecorator"));
    }
  }
}